/*
	Copyright (C) 2011 Salil Bhagurkar

	This file is part of illusion

	illusion is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as
	published by the Free Software Foundation, either version 3 of
	the License, or (at your option) any later version.

	illusion is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public
	License along with illusion. 
	If not, see <http://www.gnu.org/licenses/>.
*/

#include <klib/lib.h>
#include <klib/format.h>
#include <kernel/list.h>
#include <kernel/fs.h>
#include <kernel/init.h>
#include <kernel/errors.h>
#include <kernel/kmalloc.h>
#include <kernel/console.h>

/*
 * A place to hang out for all the file system drivers
 */

static struct fs_driver *fs_drivers, *fs_drivers_tail;

struct vfs_node *vfs_fs;

void register_fs_driver(struct fs_driver *driver)
{
	list_attach(fs_drivers, fs_drivers_tail, driver);
	struct vfs_node *driver_fs = vfs_create(vfs_fs, driver->name);
	driver_fs->class = (void *)driver;
	driver->fs = driver_fs;
}

void unregister_fs_driver(struct fs_driver *driver)
{
	list_detach(fs_drivers, fs_drivers_tail, driver);
	vfs_delete(driver->fs);
}

int detect_fs(struct vfs_node *dev, struct vfs_node *fs_fs)
{
	struct fs_driver *driver = (struct fs_driver *)fs_fs->class;
	return driver->detect_fs(dev);
}

static int fs_sync_node(struct vfs_node *node)
{
	struct fs_association *assoc = (struct fs_association *)node->dev->class;
	return assoc->driver->sync_node(assoc->dev, node);
}

static int fs_sync_page(struct vfs_node *node, struct vfs_page *page)
{
	struct fs_association *assoc = (struct fs_association *)node->dev->class;
	return assoc->driver->sync_page(assoc->dev, node, page);
}

static struct vfs_node *fs_get_child(struct vfs_node *node)
{
	struct fs_association *assoc = (struct fs_association *)node->dev->class;
	struct vfs_node *child = assoc->driver->get_child(assoc->dev, node);
	return child;
}

static struct vfs_node *fs_next_child(struct vfs_node *node)
{
	struct fs_association *assoc = (struct fs_association *)node->dev->class;
	return assoc->driver->next_child(assoc->dev, node);
}

static struct vfs_sync_provider fs_prov = {
		.flags = VFS_SP_NOPAGECACHE,
		.name = "fs_prov",
		.sync_node = fs_sync_node,
		.sync_page = fs_sync_page,
		.get_child = fs_get_child,
		.next_child = fs_next_child,
};

int mount_fs(struct vfs_node *dev, struct vfs_node *fs_fs)
{
	struct fs_driver *driver = (struct fs_driver *)fs_fs->class;
	struct fs_association *assoc = (struct fs_association *)kmalloc(sizeof(struct fs_association), "fsa");
	assoc->dev = dev;
	assoc->driver = driver;

	//Create a new device
	char name[VFS_DEV_NAME_LEN];
	format_string(name, VFS_DEV_NAME_LEN, "%s_files", dev->name);
	struct vfs_device *files_dev = vfs_create_device(name, 1);
	//We should be able to pick up the association from the new device
	files_dev->class = assoc;

	int err = driver->mount_fs(dev, files_dev->root);
	if(err) {
		vfs_remove_device(name, false);
		kfree(assoc, sizeof(struct fs_association), "fsa");
		return err;
	}

	vfs_set_sync_provider(files_dev, &fs_prov);
	return 0;
}
